#!/usr/bin/env python

import mimetypes
import os,sys

from liwe.module import LiweModule

from os3.utils.image_handler import ImageHandler

FM_FILE_EXISTS = 1
FM_NOT_FOLDER  = 2

class FileManager ( LiweModule ):
	# {{{ __init__ ( liwe )
	def __init__ ( self, liwe ):
		super ( FileManager, self ).__init__ ( liwe, "file_manager" )

		self.ih = ImageHandler ()

		self.dbm = self.liwe.dbm
		self.basedir = os.path.join ( "site", "file_manager" ) 

		self._node_fields = [ "id_node", "uid", "id_parent", "name", "folder", "size", "created", "module", "id_obj" ]
	# }}}

	# =================================================================================
	# PRIVATE METHODS
	# =================================================================================

	# {{{ _guess_mime ( name ) - Returns the file MIME name
	def _guess_mime ( self, name ):
		mime = mimetypes.guess_type ( name ) [ 0 ]
		if not mime:
			mime = "application/octet-stream"
		return mime
	# }}}
	# {{{ _get_mime ( name ) - Returns the right id_mime
	def _get_mime ( self, name ):
		mime = self._guess_mime ( name )
		res = self.dbm.single_row ( 'fm_mime', 'id_mime', "name = '%s'" % mime, safe = True )
		if not res:
			self.dbm.insert ( 'fm_mime', { 'name' : mime } )
			return self.dbm.insert_id ()
		return res [ 'id_mime' ]
	# }}}
	# {{{ _get_mime_name ( id_mime ) - Returns MIME name
	def _get_mime_name ( self, id_mime ):
		res = self.dbm.single_row ( 'fm_mime', 'name', "id_mime = '%s'" % id_mime, safe = True )
		return res.get ( "name", "unkn/unkn" )
	# }}}
	# {{{ _get_path ( file_id ) - Returns the internal file path
	def _get_path ( self, file_id ):
		m1 = str ( file_id % 1000 )
		
		return os.path.join ( self.basedir, m1, m1 )
	# }}}
	# {{{ _get_file_name ( file_id ) - Returns the internal file path
	def _get_file_name ( self, file_id ):
		return os.path.join ( self._get_path ( file_id ), str ( file_id ) )
	# }}}
	# {{{ _save_file ( file_id, file_obj ) - Saves the current file object
	def _save_file ( self, file_id, file_obj, id_mime ):
		path = self._get_path ( file_id )

		try:
			os.makedirs ( path )
		except Exception as ex:
			# If errno == 17 => dir exists (not an error)
			if ex.errno != 17:
				self.liwe.pyhp.log.error ( 'file_manager -> os.makedirs failed: %s' % ex )
				return -1

		fname = os.path.join ( path, str ( file_id ) )

		try:
			fd = open ( fname, "wb" )
			fd.write ( file_obj.read () )
			fd.close ()

			fd = open ( fname, "r" )
			fd.seek ( 0, os.SEEK_END )
			size = fd.tell ()
			fd.close ()

		except Exception as ex:
			self.liwe.pyhp.log.error ( 'file_manager -> save failed: %s' % ex )
			return -1

		self._create_thumb ( file_id, id_mime )
			
		return size
	# }}}
	# {{{ _get_uid () - Returns the current uid
	def _get_uid ( self ):
		uid = self.liwe.get_uid ()
		if uid < 0: uid = 0

		return uid
	# }}}
	# {{{ _thumb_img ( id_node ) - Creates a thumb for an image file
	def _thumb_img ( self, id_node ):
		img = self.ih.load ( open ( self._get_file_name ( id_node ), "rb" ) )
		if not img: return 

		if img: img.toRGB ()
		dest_path = self._get_file_name ( id_node ) + ".thumb.jpg"

		# Scalo l'immagine in dimensone FULL 
		img.scale ( self.cfg.get ( "thumb.width", 128 ), self.cfg.get ( "thumb.height", 128 ) )

		# Salvo l'immagine FULL nel path definitivo
		img.save ( dest_path, "JPEG" )
	# }}}
	# {{{ _create_thumb ( id_node, id_mime ) - Creates thumbnail for file
	def _create_thumb ( self, id_node, id_mime ):
		mime_name = self._get_mime_name ( id_mime )

		if mime_name.find ( "image" ) != -1:
			self._thumb_img ( id_node )
	# }}}
	def _canonify ( self, name ):
		return name.replace ( "/", "-" ).replace ( "'", "" )

	# =================================================================================
	# PUBLIC METHODS
	# =================================================================================

	# {{{ exists ( name, id_folder = 0 ) - Checks if a file / dir exists in the given id_folder
	def exists ( self, name, id_folder = 0 ):
		"""
		exists ( name, id_folder = 0 ) -> True / False

		This method returns ``True`` if ``name`` exists in the current ``id_folder``.

		.. seealso:: - get_id_node()
		"""
		res = self.get_id_node ( name, id_folder )
		if res == -1: return False

		return True
	# }}}
	# {{{ get_id_node ( name, id_folder = 0 ) - Returns the id_node associated to name / id_folder
	def get_id_node ( self, name, id_folder = 0 ):
		"""
		get_id_node ( name, id_folder = 0 ) -> id_node / -1

		This method returns the ``id_node`` associated to the given file or dir binded to the
		``name`` and ``id_folder``.

		If the file / dir does not exist, ``-1`` is returned.

		.. seealso:: - exists ()
		"""
		name = self._canonify ( name )
		res = self.dbm.single_row ( 'fm_node', 'id_node', "name = '%s' AND id_parent = %s" % ( name, id_folder ), safe = True )
		return res.get ( "id_node", -1 )
	# }}}
	# {{{ folder_create ( folder_name, id_parent = 0, module = '', id_obj = '' )
	def folder_create ( self, folder_name, id_parent = 0, module = '', id_obj = '' ):
		"""
		folder_create ( folder_name, id_parent = 0, module = '', id_obj = '' ) -> id_node / -1

		This method creates a new folder called ``folder_name`` under ``id_parent``.
		If ``id_parent`` is 0, the folder will be created on the ``root`` filesystem.

		.. params::

			- ``folder_name``	- Name of the folder to be created
			- ``id_parent``		- (optional) ``id_node`` of the containing folder ( 0 == ``root`` )
			- ``module``		- (optional) The ``module`` name
			- ``id_obj``		- (optional) The associated ID of the object owning the file
		
		"""
		if self.exists ( folder_name, id_parent ):
			return -1

		folder_name = self._canonify ( folder_name )

		dct = {
			'uid' : self._get_uid (),
			'id_parent' : id_parent,
			'id_mime' : 0,
			'name' : folder_name,
			'folder' : 1,
			'created' : '*NOW()',
			'module' : module,
			'id_obj' : id_obj
		}

		self.dbm.insert ( 'fm_node', dct )

		return self.dbm.insert_id ()
	# }}}
	# {{{ file_add ( id_folder, file_name, file_obj, module = '', id_obj = '' )
	def file_add ( self, id_folder, file_name, file_obj, module = '', id_obj = '' ):
		"""
		file_add ( id_folder, file_name, file_obj, module = '', id_obj = '' ) -> id_node / -1

		This method adds a new file in the file system under the given ``id_folder``.
		The file will be called ``file_name`` and read by ``file_obj`` (which must be a valid File IO)
		Optionally, you can specify ``module`` name and ``id_obj``.

		"""
		if self.exists ( file_name, id_folder ):
			return -1

		file_name = self._canonify ( file_name )

		dct = {
			'uid' : self._get_uid (),
			'id_parent' : id_folder,
			'id_mime' : self._get_mime ( file_name ),
			'name' : file_name,
			'folder' : 0,
			'size' : 0,
			'created' : '*NOW()',
			'module' : module,
			'id_obj' : id_obj
		}

		self.dbm.insert ( 'fm_node', dct )
		file_id = self.dbm.insert_id ()

		size = self._save_file ( file_id, file_obj, dct [ 'id_mime' ] )
		if size != -1:
			self.dbm.update ( 'fm_node', { 'size' : size }, "id_node = '%s'" % file_id )
			return file_id
		else:
			self.dbm.delete ( 'fm_node', "id_node = '%s'" % file_id )
			return -1
	# }}}
	# {{{ stat_multi ( id_nodes, full_path = False )
	def stat_multi ( self, id_nodes, full_path = False ):
		if not isinstance ( id_nodes, list ):
			id_nodes = [ str ( id_nodes ) ]

		res = self.dbm.all_rows ( "fm_node", self._node_fields, "id_node IN ( %s )" % ",".join ( id_nodes ), safe = True )

		if full_path:
			for r in res:
				r [ 'full_path' ] = self._get_file_name ( r [ 'id_node' ] )

		return res
	# }}}
	# {{{ stat ( id_node, full_path = False )
	def stat ( self, id_node, full_path = False ):
		res = self.dbm.single_row ( "fm_node", self._node_fields, "id_node = %s" % id_node, safe = True )

		if full_path:
			res [ 'full_path' ] = self._get_file_name ( res [ 'id_node' ] )

		return res
	# }}}
	# {{{ node_from_path ( path ) - Returns the id_node for the given path
	def node_from_path ( self, path ):
		nodes = [ x for x in path.split ( "/" ) if x ]

		id_folder = 0
		for n in nodes:
			id_folder = self.get_id_node ( n, id_folder )
			if id_folder == -1: return -1

		return id_folder
	# }}}
	# {{{ remove ( id_node ) - Removes a file / dir from file system
	def remove ( self, id_node ):
		node = self.stat ( id_node )
		if not node: return

		if node [ 'folder' ]:
			nodes = self.ls ( node [ 'id_node' ] )
			for n in nodes:
				self.remove ( n [ 'id_node' ] )
		else:
			os.remove ( self._get_file_name ( id_node ) )

		self.dbm.delete ( "fm_node", "id_node = %s" % id_node )
	# }}}
	# {{{ rename ( id_node, new_name ) - Renames a file / dir
	def rename ( self, id_node, name ):
		node = self.stat ( id_node )
		if not node: return

		name = self._canonify ( name )

		if self.exists ( name, node [ 'id_parent' ] ):
			return FM_FILE_EXISTS

		self.dbm.update ( "fm_node", { "name" : name }, "id_node = %s" % id_node )
	# }}}
	# {{{ move ( id_folder_dest, id_node ) - Moves a file / dir inside another folder
	def move ( self, id_folder_dest, id_node ):
		fold = self.stat ( id_folder_dest )
		if not fold: return
		if not fold [ 'folder' ]: 
			return FM_NOT_FOLDER

		self.dbm.update ( "fm_node", { "id_parent" : id_folder_dest }, "id_node = %s" % id_node )
	# }}}
	# {{{ ls ( id_folder = 0, show_dirs = True, show_files = True, module = '', id_obj = 0 )
	def ls ( self, id_folder = 0, show_dirs = True, show_files = True, module = '', id_obj = 0 ):
		conds = [ "id_parent = %s" % id_folder ]

		if module: conds.append ( "module = '%s'" % module )
		if id_obj: conds.append ( "id_obj = '%s'" % id_obj )

		if show_dirs and show_files:
			pass
		else:
			if show_dirs: conds.append ( "folder = 1" )
			if show_files: conds.append ( "folder = 0" )

		extras = "ORDER BY folder DESC, name"

		return self.dbm.all_rows ( "fm_node", self._node_fields, ' AND '.join ( conds ), extras = extras, safe = True )
	# }}}
        # {{{ ds ( id_folder = 0, show_dirs = True, show_files = True, module = '', id_obj = 0, start_point = 0, limit = 10 )
        def ds ( self, id_folder = 0, show_dirs = True, show_files = True, module = '', date_start = '', date_end = '', id_obj = 0, start_point = 0, limit = 10 ):
                conds = [ "id_parent = %s" % id_folder ]
        
                if module: conds.append ( "module = '%s'" % module )
                if id_obj: conds.append ( "id_obj = '%s'" % id_obj )
        
                if show_dirs and show_files:
                        pass 
                else:
                        if show_dirs: conds.append ( "folder = 1" )
                        if show_files: conds.append ( "folder = 0" )
		#FIXME:form's calendar plugin always set value to date fields. Normally they both have current date as value.
		if date_start != date_end:
			conds.append ( "created BETWEEN CAST( '%s' AS DATE ) AND CAST( '%s' AS DATE )" % ( date_start, date_end ) )
			
                extras = [ "ORDER BY created DESC, name" ]
                tot = self.dbm.count ( "fm_node", self._node_fields, ' AND '.join ( conds ), extras = ' '.join ( extras ) )
                
                extras.append ( "LIMIT %s, %s" % ( start_point, limit ) )
                rows = self.dbm.all_rows ( "fm_node", self._node_fields, ' AND '.join ( conds ), extras = ' '.join ( extras ), safe = True, debug = self.debug () )

                return { 'tot_rows' : tot, 'rows' : rows }
        # }}} 

if __name__ == '__main__':
        from liwe import liwe
        from liwe.fake import FakePyHP
	import sys

        l = liwe ( pyhp = FakePyHP (), home_dir = 'work' )

	fm = FileManager ( l )
	fm.basedir = os.path.join ( "work", "test" )

	folder_id = fm.folder_create ( "prova 2" )
	fm.file_add ( folder_id, "file di prova", open ( "file_manager_class.py" ) )
	fm.file_add ( folder_id, "test.sql", open ( "work/file_manager.sql" ) )

	print fm.exists ( "prova 2" )
	print fm.ls ( folder_id )
	print fm.remove ( folder_id )
	
